﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Collections;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;

using EnvDTE;
using EnvDTE80;

using Microsoft.WowAddonStudio.FrameXml.Components;
using Microsoft.WowAddonStudio.FrameXml.Components.Behaviors;
using Microsoft.WowAddonStudio.FrameXml.Components.Events.ScriptManager;
using Microsoft.WowAddonStudio.FrameXml.Components.Layout;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Design.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Design.Support;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.Attributes;
using Microsoft.WowAddonStudio.FrameXml.Services;

namespace Microsoft.WowAddonStudio.FrameXml.Design
{
	/// <summary>
	/// Provides an implementation of the System.ComponentModel.Design.Serialization.IDesignerLoaderService interface.
	/// </summary>
	public class FrameXmlDesignerLoader : BasicDesignerLoader, IFrameXmlDesignerService
	{
        private readonly List<DesignerBehavior> behaviors = new List<DesignerBehavior>();
        private readonly FrameXmlSerializer serializer;

		/// <summary>
		/// Initializes a new instance of the <see cref="FrameXmlDesignerLoader"/> class.
		/// </summary>
		/// <param name="serializer">The serializer.</param>
		public FrameXmlDesignerLoader(FrameXmlSerializer serializer)
		{
			if (serializer == null)
				throw new ArgumentNullException("serializer");

			this.serializer = serializer;
			DocumentMoniker = serializer.DocumentMoniker;
		}

		#region IFrameXmlDesignerService Members

		/// <summary>
		/// Gets or sets the host.
		/// </summary>
		/// <value>The host.</value>
		public IServiceProvider Host
		{
			get { return LoaderHost; }
		}

		/// <summary>
		/// Gets or sets the document moniker.
		/// </summary>
		/// <value>The document moniker.</value>
		public string DocumentMoniker{get; private set;}

	    public bool GetDirty()
	    {
	        return true;
	    }

	    /// <summary>
        /// Sets the dirty flag on the underlying document.
        /// </summary>
        /// <param name="isDirty">True, if the document is dirty.</param>
	    public void SetDirty(bool isDirty)
	    {
            // TODO: Implement this correctly

            // Immediately flush FrameXML to document buffer, if dirty
            if (isDirty)
                this.Flush();

            //TODO: Make this implementation work (dirty flag is set, but it is automatically cleared
            //after some time.)
            //MS BUG? See thread at http://www.codecomments.com/archive358-2006-2-796278.html
            //var rdt = (IVsRunningDocumentTable)GetService(typeof(SVsRunningDocumentTable));
            //var designerService = GetService<IFrameXmlDesignerService>();

            //if (rdt != null && designerService != null)
            //{
            //    //We have the necessary services to proceed...
            //    uint dwCookie = 0;
            //    try
            //    {
            //        IntPtr xmlDocData;
            //        uint xmlItemid;
            //        IVsHierarchy projectHierarchy;
            //        int ret = rdt.FindAndLockDocument((uint)_VSRDTFLAGS.RDT_NoLock, designerService.DocumentMoniker,
            //                                          out projectHierarchy,
            //                                          out xmlItemid, out xmlDocData, out dwCookie);

            //        if (ret == VSConstants.S_OK)
            //        {
            //            //Document is found and locked, now set the dirty flag
            //            rdt.NotifyDocumentChanged(dwCookie, (int)__VSRDTATTRIB.RDTA_DocDataIsDirty);

            //            var shell = (IVsUIShell)GetService<SVsUIShell>();

            //            if (shell != null)
            //            {
            //                shell.UpdateDocDataIsDirtyFeedback(dwCookie, 1);
            //            }
            //        }
            //    }
            //    finally
            //    {
            //        rdt.UnlockDocument((uint)_VSRDTFLAGS.RDT_NoLock, dwCookie);					
            //    }
            //}
	    }

	    /// <summary>
		/// Displays the specified error message in a message box.
		/// </summary>
		/// <param name="message">The message.</param>
		public void ShowError(string message)
		{
			ShowError(null, message);
		}

		/// <summary>
		/// Displays the specified error message in a message box.
		/// </summary>
		/// <param name="message">The message.</param>
		public void NotifyError(string message)
		{
			NotifyMessage(message);
		}

		/// <summary>
		/// Displays the specified message in a message box.
		/// </summary>
		/// <param name="message">The message.</param>
		public void NotifyMessage(string message)
		{
			var service = (DTE2)GetService<DTE>();
			if(service != null)
			{
				service.StatusBar.Text = message;
				service.StatusBar.Highlight(true);
			}
		}

		/// <summary>
		/// Displays the specified exception and information about the exception in a message box.
		/// </summary>
		/// <param name="ex">The ex.</param>
		/// <param name="message">The message.</param>
		public void ShowError(Exception ex, string message)
		{
			var service = GetService<IUIService>();
			if(service != null)
			{
				if(ex != null)
				{
					if (!string.IsNullOrEmpty(message))
						service.ShowError(ex, message);
					else
						service.ShowError(ex);
				}
				else
					service.ShowError(message);
			}
		}

		/// <summary>
		/// Displays the specified message in a message box.
		/// </summary>
		/// <param name="message">The message.</param>
		public void ShowMessage(string message)
		{
			var service = GetService<IUIService>();
			if (service != null)
			{
				service.ShowMessage(message);
			}
		}

		/// <summary>
		/// Attempts to display the specified form in a dialog box.
		/// </summary>
		/// <param name="form">The System.Windows.Forms.Form to display.</param>
		/// <returns>
		/// One of the System.Windows.Forms.DialogResult values indicating the result code returned by the dialog box.
		/// </returns>
		public DialogResult ShowDialog(Form form)
		{
			var service = GetService<IUIService>();
			if (service != null)
			{
				return service.ShowDialog(form);
			}
			return DialogResult.Cancel;
		}

		#endregion

		/// <summary>
		/// Initializes services.
		/// </summary>
		/// <exception cref="T:System.InvalidOperationException">The <see cref="T:System.ComponentModel.Design.Serialization.IDesignerLoaderHost"/> has not been initialized.</exception>
		/// <exception cref="T:System.ObjectDisposedException">The <see cref="T:System.ComponentModel.Design.Serialization.IDesignerLoaderHost"/> has been disposed.</exception>
		protected override void Initialize()
		{
			base.Initialize();

			LoaderHost.AddService(typeof(IFrameXmlDesignerService), this);

			// Add FrameXML layout engine and FrameXML component factory
			LoaderHost.AddService(typeof (FrameXmlLayoutEngine), new FrameXmlLayoutEngine());
			LoaderHost.AddService(typeof (FrameXmlComponentFactory), new FrameXmlComponentFactory(new LoaderHostComponentActivator(LoaderHost), this.Host));

			// Add FrameXmlComponentSerializationService service instance
			//NOTE: do not add to the container as type of ComponentSerializationService
			LoaderHost.AddService(typeof(IFrameXmlComponentSerializationService), new FrameXmlComponentSerializationService(LoaderHost, new FrameXmlRuntimeSerializer()));

			IEventBindingService eventService = new FrameXmlEventBindingService(LoaderHost);
			LoaderHost.AddService(typeof(IEventBindingService), eventService);

			IFrameXmlScriptManager scriptManager = new FrameXmlScriptManager(LoaderHost);
			LoaderHost.AddService(typeof(IFrameXmlScriptManager), scriptManager);

			IDesignerSerializationService designerSerializationService = new FrameXmlDesignerSerializationService(LoaderHost, LoaderHost.Container);
			LoaderHost.AddService(typeof(IDesignerSerializationService), designerSerializationService);

			AddPropertyValueUIHandler();

		    this.InitializeBehaviors();
		}

	    private void InitializeBehaviors()
	    {
            var designerHost = (IDesignerHost)this.Host;

	        var inheritedBehavior = new InheritedControlCommandDesignerBehavior(designerHost);
            inheritedBehavior.Initialize();

	        behaviors.Add(inheritedBehavior);
	    }

	    /// <summary>
		/// Loads a designer from persistence.
		/// </summary>
		/// <param name="serializationManager">An <see cref="T:System.ComponentModel.Design.Serialization.IDesignerSerializationManager"/> to use for loading state for the designers.</param>
		protected override void PerformLoad(IDesignerSerializationManager serializationManager)
		{
			ICollection errorCollection = null;

			try
			{
				var frameXmlComponentFactory = GetService<FrameXmlComponentFactory>();

				// Perform the deserialization
				Ui ui = Serializer.Deserialize();

				// Create the components using the component factory
				frameXmlComponentFactory.CreateComponent(ui);
			}
			catch (Exception exception)
			{
				errorCollection = new ArrayList {exception};
			}

			// Let the LoaderHost know that we finished loading the Ui
			LoaderHost.EndLoad("UserControl", errorCollection == null, errorCollection);
		}

		/// <summary>
		/// Flushes all changes to the designer.
		/// </summary>
		/// <param name="serializationManager">An <see cref="T:System.ComponentModel.Design.Serialization.IDesignerSerializationManager"/> to use for persisting the state of loaded designers.</param>
		protected override void PerformFlush(IDesignerSerializationManager serializationManager)
		{
			// Get the root Ui component
			var uiControl = LoaderHost.RootComponent as UiControl;
			if (uiControl != null)
			{
				// Get the underlying Ui serialization object
				var ui = uiControl.Ui;

				// Disable inheritance during serialization
			    this.InheritanceEnabled = false;

				// Perform the serialization
				Serializer.Serialize(ui);

				// Enable inheritance
                this.InheritanceEnabled = true;
			}
		}

        /// <summary>
        /// Gets or sets a value indicating whether inheritance is enabled in the components
        /// hosted by the designer associated with this loader.
        /// </summary>
	    public bool InheritanceEnabled
	    {
            get
            {
                return ((InheritableObject)((IFrameXmlComponent) LoaderHost.RootComponent).SerializationObject).InheritanceEnabled;
            }
            set
            {
                var inheritableObjects = LoaderHost.Container.Components.OfType<IFrameXmlComponent>().Select(component => component.SerializationObject).OfType<InheritableObject>();
                foreach (var inheritableObject in inheritableObjects)
                    inheritableObject.InheritanceEnabled = value;
            }
	    }
		/// <summary>
		/// Gets or sets a value indicating whether the designer has been modified.
		/// </summary>
		/// <value></value>
		/// <returns>true if the designer has been modified; otherwise, false,</returns>
		protected override bool Modified
		{
			get
			{
				//HACK: To get the IsDirty flag work...
				return true;
			}
			set
			{
				base.Modified = value;
			}
		}

		/// <summary>
		/// Gets the serializer.
		/// </summary>
		/// <value>The serializer.</value>
		internal FrameXmlSerializer Serializer
		{
			get { return serializer; }
		}

		/// <summary>
		/// Gets the service.
		/// </summary>
		/// <typeparam name="TService">The type of the service.</typeparam>
		/// <returns></returns>
		private TService GetService<TService>()
		{
			return (TService) GetService(typeof (TService));
		}

		/// <summary>
		/// Adds the property value UI handler.
		/// </summary>
		private void AddPropertyValueUIHandler()
		{
			var uiService = GetService<IPropertyValueUIService>();

			if (uiService != null)
				uiService.AddPropertyValueUIHandler(InheritedPropertyValueUIHandler);
		}

		/// <summary>
		/// Removes the property value UI handler.
		/// </summary>
		private void RemovePropertyValueUIHandler()
		{
			var uiService = GetService<IPropertyValueUIService>();

			if (uiService != null)
				uiService.RemovePropertyValueUIHandler(InheritedPropertyValueUIHandler);
		}

		/// <summary>
		/// Inheriteds the property value UI handler.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <param name="propertyDescriptor">The property descriptor.</param>
		/// <param name="itemList">The item list.</param>
		private static void InheritedPropertyValueUIHandler(ITypeDescriptorContext context, PropertyDescriptor propertyDescriptor, ArrayList itemList)
		{
			if (propertyDescriptor.Attributes.OfType<Attribute>().
				Any(attribute => attribute is InheritedAttribute && ((InheritedAttribute)attribute).Inherited))
			{
				Bitmap bitmap = Resources.InheritedGlyph;
				bitmap.MakeTransparent();

				// Add glyph, no click event handler for now
				itemList.Add(new PropertyValueUIItem(bitmap, delegate { }, "Inherited Property"));
			}
		}

		/// <summary>
		/// Releases the resources used by the 
		/// <see cref="T:System.ComponentModel.Design.Serialization.BasicDesignerLoader"></see>.
		/// </summary>
		public override void Dispose()
		{
			try
			{
			    foreach (var behavior in behaviors)
			        behavior.Dispose();

				RemovePropertyValueUIHandler();
			}
			finally
			{
				base.Dispose();
			}
		}
	}
}